import python

/** A variable, either a global or local variable (including parameters) */
class Variable extends @py_variable {
    Variable() {
        exists(string name |
            variable(this, _, name) and
            not name = "*" and
            not name = "$"
        )
    }

    /** Gets the identifier (name) of this variable */
    string getId() { variable(this, _, result) }

    string toString() { result = "Variable " + this.getId() }

    /** Gets an access (load or store) of this variable */
    Name getAnAccess() {
        result = this.getALoad()
        or
        result = this.getAStore()
    }

    /** Gets a load of this variable */
    Name getALoad() { result.uses(this) }

    /** Gets a store of this variable */
    Name getAStore() { result.defines(this) }

    /** Gets a use of this variable */
    NameNode getAUse() { result.uses(this) }

    /** Gets the scope of this variable */
    Scope getScope() { variable(this, result, _) }

    /**
     * Whether there is an access to this variable outside
     * of its own scope. Usually occurs in nested functions
     * or for global variables.
     */
    predicate escapes() { exists(Name n | n = this.getAnAccess() | n.getScope() != this.getScope()) }

    /** Whether this variable is a parameter */
    predicate isParameter() { none() }

    predicate isSelf() { none() }
}

/** A local (function or class) variable */
class LocalVariable extends Variable {
    LocalVariable() {
        exists(Scope s | s = this.getScope() | s instanceof Function or s instanceof Class)
    }

    override string toString() { result = "Local Variable " + this.getId() }

    /** Whether this variable is a parameter */
    override predicate isParameter() { exists(Parameter p | this.getAnAccess() = p) }

    /** Holds if this variable is the first parameter of a method. It is not necessarily called "self" */
    override predicate isSelf() {
        exists(Function f, Parameter self |
            this.getAnAccess() = self and
            f.isMethod() and
            f.getArg(0) = self
        )
    }
}

/**
 * A local variable that uses "load fast" semantics, for lookup:
 * If the variable is undefined, then raise an exception.
 */
class FastLocalVariable extends LocalVariable {
    FastLocalVariable() { this.getScope() instanceof FastLocalsFunction }
}

/**
 * A local variable that uses "load name" semantics, for lookup:
 * If the variable is undefined, then lookup the value in globals().
 */
class NameLocalVariable extends LocalVariable {
    NameLocalVariable() { not this instanceof FastLocalVariable }
}

/** A global (module-level) variable */
class GlobalVariable extends Variable {
    GlobalVariable() { exists(Module m | m = this.getScope()) }

    override string toString() { result = "Global Variable " + this.getId() }
}
